/*==============================================================================
            Copyright (c) 2013 Moritz Hader, Marcel Schrder
            All Rights Reserved.

	@file  HeimdallGLUtilities.cpp

	@brief Provides basic methods used when processing OpenGL-Tasks. Among those
	methods are
	*Error-Decoding
	*Generation of stock-shapes (Cube, Grid)
	*Loading of 3-dimensional Vertex-Arrays from java
   
   This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

==============================================================================*/


#include <egl/HeimdallGLUtilities.h>
#include <egl/VertexManagementUnit.h>
#include <models/Cone.h>
#include <models/Barrier.h>
#include <models/TrafficLight.h>
#include <models/FancyCar.h>


bool CheckShadersForError(GLuint handle){

	GLint error = 0;

	glGetShaderiv(handle, GL_COMPILE_STATUS, &error); //receive the shader information vector

	if(error == GL_FALSE){
		__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error, an error occurred while compiling the shaders.");
		return false;
	}

	return true;
}

bool CheckProgrammForError(GLuint handle){

	GLint error = 0;

	glGetProgramiv(handle, GL_LINK_STATUS, &error); //receive the program information vector

	if(error == GL_FALSE){
		__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error, an error occurred while compiling the shader-program.");
		return false;
	}

	return true;
}

void CheckGlError(const char* operation)
{
	for (GLint error = glGetError(); error; error = glGetError())
		__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "after %s() glError (0x%x)", operation, error);
}

void GenSimpleCube(VertexManagementUnit** vertMU, float width){

	__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Generating a Simple Cube");

	//Initialize (set the address of vmu to a new Object)
	VertexManagementUnit* vmu = new VertexManagementUnit(GL_TRIANGLES,36);
	*vertMU = vmu;

	//Meh, is this type of access really efficient?
	H3DVector vertex, normal;
	float coord = width/2;

	//Back
	normal[0] = 0.0f; normal[1] = 1.0f; normal[2] = 0.0f;

	vertex[0] = coord; vertex[1] = coord; vertex[2] = coord;
		vmu->AddVertex(vertex);

	vertex[0] = coord; vertex[1] = coord; vertex[2] = -coord;
		vmu->AddVertex(vertex);

	vertex[0] = -coord; vertex[1] = coord; vertex[2] = -coord;
		vmu->AddVertex(vertex);

	vertex[0] = coord; vertex[1] = coord; vertex[2] = coord;
		vmu->AddVertex(vertex);

	vertex[0] = -coord; vertex[1] = coord; vertex[2] = -coord;
		vmu->AddVertex(vertex);

	vertex[0] = -coord; vertex[1] = coord; vertex[2] = coord;
		vmu->AddVertex(vertex);

	//As the previous coordinates defined one surface, the normals are all the same
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);


	//Front
	normal[0] = 0.0f; normal[1] = -1.0f; normal[2] = 0.0f;

	vertex[0] = -coord; vertex[1] = -coord; vertex[2] = -coord;
		vmu->AddVertex(vertex);

	vertex[0] = coord; vertex[1] = -coord; vertex[2] = -coord;
		vmu->AddVertex(vertex);

	vertex[0] = coord; vertex[1] = -coord; vertex[2] = coord;
		vmu->AddVertex(vertex);

	vertex[0] = -coord; vertex[1] = -coord; vertex[2] = coord;
		vmu->AddVertex(vertex);

	vertex[0] = -coord; vertex[1] = -coord; vertex[2] = -coord;
		vmu->AddVertex(vertex);

	vertex[0] = coord; vertex[1] = -coord; vertex[2] = coord;
		vmu->AddVertex(vertex);

	//As the previous coordinates defined one surface, the normals are all the same
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);


	//Left
	normal[0] = -1.0f; normal[1] = 0.0f; normal[2] = 0.0f;

	vertex[0] = -coord; vertex[1] = coord; vertex[2] = coord;
		vmu->AddVertex(vertex);

	vertex[0] = -coord; vertex[1] = coord; vertex[2] = -coord;
		vmu->AddVertex(vertex);

	vertex[0] = -coord; vertex[1] = -coord; vertex[2] = -coord;
		vmu->AddVertex(vertex);

	vertex[0] = -coord; vertex[1] = coord; vertex[2] = coord;
		vmu->AddVertex(vertex);

	vertex[0] = -coord; vertex[1] = -coord; vertex[2] = -coord;
		vmu->AddVertex(vertex);

	vertex[0] = -coord; vertex[1] = -coord; vertex[2] = coord;
		vmu->AddVertex(vertex);

	//As the previous coordinates defined one surface, the normals are all the same
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);


	//Rigth
	normal[0] = 1.0f; normal[1] = 0.0f; normal[2] = 0.0f;

	vertex[0] = coord; vertex[1] = -coord; vertex[2] = -coord;
		vmu->AddVertex(vertex);

	vertex[0] = coord; vertex[1] = coord; vertex[2] = -coord;
		vmu->AddVertex(vertex);

	vertex[0] = coord; vertex[1] = coord; vertex[2] = coord;
		vmu->AddVertex(vertex);

	vertex[0] = coord; vertex[1] = coord; vertex[2] = coord;
		vmu->AddVertex(vertex);

	vertex[0] = coord; vertex[1] = -coord; vertex[2] = coord;
		vmu->AddVertex(vertex);

	vertex[0] = coord; vertex[1] = -coord; vertex[2] = -coord;
		vmu->AddVertex(vertex);

	//As the previous coordinates defined one surface, the normals are all the same
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);


	//Top
	normal[0] = 0.0f; normal[1] = 0.0f; normal[2] = 1.0f;

	vertex[0] = coord; vertex[1] = -coord; vertex[2] = coord;
		vmu->AddVertex(vertex);

	vertex[0] = coord; vertex[1] = coord; vertex[2] = coord;
		vmu->AddVertex(vertex);

	vertex[0] = -coord; vertex[1] = coord; vertex[2] = coord;
		vmu->AddVertex(vertex);

	vertex[0] = -coord; vertex[1] = coord; vertex[2] = coord;
		vmu->AddVertex(vertex);

	vertex[0] = -coord; vertex[1] = -coord; vertex[2] = coord;
		vmu->AddVertex(vertex);

	vertex[0] = coord; vertex[1] = -coord; vertex[2] = coord;
		vmu->AddVertex(vertex);

	//As the previous coordinates defined one surface, the normals are all the same
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);

	//Bottom
	normal[0] = 0.0f; normal[1] = 0.0f; normal[2] = -1.0f;

	vertex[0] = coord; vertex[1] = -coord; vertex[2] = -coord;
		vmu->AddVertex(vertex);

	vertex[0] = -coord; vertex[1] = -coord; vertex[2] = -coord;
		vmu->AddVertex(vertex);

	vertex[0] = -coord; vertex[1] = coord; vertex[2] = -coord;
		vmu->AddVertex(vertex);

	vertex[0] = -coord; vertex[1] = coord; vertex[2] = -coord;
		vmu->AddVertex(vertex);

	vertex[0] = coord; vertex[1] = coord; vertex[2] = -coord;
		vmu->AddVertex(vertex);

	vertex[0] = coord; vertex[1] = -coord; vertex[2] = -coord;
		vmu->AddVertex(vertex);

	//As the previous coordinates defined one surface, the normals are all the same
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);
	vmu->AddNormal(normal);

	//vmu->Finalize();
	CheckGlError("GenSimpleCube");
}

void GenSimpleGrid(VertexManagementUnit** vertMU, float width, float step){

	__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Generating a Grid");

	H3DVector vertex;
	float  span = width/2;
	int vertexCount = ((width/step)+1)*4; //How many elements will we need (four for every step)

	//Initialize (set the address of vmu to a new Object)
	VertexManagementUnit* vmu = new VertexManagementUnit(GL_LINES,vertexCount);
	*vertMU = vmu;

	for(float x = -span; x <= span; x+= step) {

		//Vertical
		vertex[0] = x; vertex[1] = span; vertex[2] = 0.0f;
			vmu->AddVertex(vertex);
		vertex[0] = x; vertex[1] = -span; vertex[2] = 0.0f;
			vmu->AddVertex(vertex);

		//Horizontal
		vertex[0] = span; vertex[1] = x; vertex[2] = 0.0f;
			vmu->AddVertex(vertex);
		vertex[0] = -span; vertex[1] = x; vertex[2] = 0.0f;
			vmu->AddVertex(vertex);

	}

	//vmu->Finalize();
	CheckGlError("GenSimpleGrid");
}

/**
 * This method generates the vertexManagementUnit that is necessary to
 * draw a simple cone. Note that the vertex-data (and normal data) are
 * direct blender-export (.obj)
 */
void GenSimpleCone(VertexManagementUnit** vertMU){

	__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Generating a Cone");

	H3DVector vertex, normal;

	//Initialize (set the address of vmu to a new Object)
	VertexManagementUnit* vmu = new VertexManagementUnit(GL_TRIANGLES,CONE_VERTICE_COUNT);
	*vertMU = vmu;

	//vmu->AddVertexArray(vertices);
	//vmu->AddNormalArray(normals);

	for(int i = 0; i<CONE_VERTICE_COUNT;i++){
		//normal[0] = normals[i][0]; normal[1] = normals[i][1]; normal[2] = normals[i][2];
		//vertex[0] = vertices[i][0]; vertex[1] = vertices[i][1]; vertex[2] = vertices[i][2];

		vmu->AddVertex(CONE_VERTICES[i]);
		vmu->AddNormal(CONE_NORMALS[i]);
	}

	CheckGlError("GenSimpleCone");
}

/**
 * This method generates the vertexManagementUnit that is necessary to
 * draw a simple Barrier. Note that the vertex-data (and normal data) are
 * direct blender-export (.obj)
 */
void GenSimpleBarrier(VertexManagementUnit** vertMU){

	__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Generating a Barrier");

	H3DVector vertex, normal;

	//Initialize (set the address of vmu to a new Object)
	VertexManagementUnit* vmu = new VertexManagementUnit(GL_TRIANGLES,BARRIER_VERTICE_COUNT);
	*vertMU = vmu;

	//vmu->AddVertexArray(vertices);
	//vmu->AddNormalArray(normals);

	for(int i = 0; i<BARRIER_VERTICE_COUNT;i++){
		//normal[0] = normals[i][0]; normal[1] = normals[i][1]; normal[2] = normals[i][2];
		//vertex[0] = vertices[i][0]; vertex[1] = vertices[i][1]; vertex[2] = vertices[i][2];

		vmu->AddVertex(BARRIER_VERTICES[i]);
		vmu->AddNormal(BARRIER_NORMALS[i]);
	}

	CheckGlError("GenSimpleBarrier");
}

/**
 * This method generates the vertexManagementUnit that is necessary to
 * draw a simple Traffic-Light. Note that the vertex-data (and normal data) are
 * direct blender-export (.obj)
 */
void GenSimpleTrafficL(VertexManagementUnit** vertMU){

	__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Generating a Traffic-Light");

	H3DVector vertex, normal;

	//Initialize (set the address of vmu to a new Object)
	VertexManagementUnit* vmu = new VertexManagementUnit(GL_TRIANGLES,TRAFFICL_VERTICE_COUNT);
	*vertMU = vmu;

	//vmu->AddVertexArray(vertices);
	//vmu->AddNormalArray(normals);

	for(int i = 0; i<TRAFFICL_VERTICE_COUNT;i++){
		//normal[0] = normals[i][0]; normal[1] = normals[i][1]; normal[2] = normals[i][2];
		//vertex[0] = vertices[i][0]; vertex[1] = vertices[i][1]; vertex[2] = vertices[i][2];

		vmu->AddVertex(TRAFFICL_VERTICES[i]);
		vmu->AddNormal(TRAFFICL_NORMALS[i]);
	}

	CheckGlError("GenSimpleTrafficL");
}

void GenSimpleFancyCar(VertexManagementUnit** vertMU){

	__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Generating a Fancy Ass Car");

	H3DVector vertex, normal;

	//Initialize (set the address of vmu to a new Object)
	VertexManagementUnit* vmu = new VertexManagementUnit(GL_TRIANGLES,FANCYCAR_VERTICE_COUNT);
	*vertMU = vmu;

	//vmu->AddVertexArray(vertices);
	//vmu->AddNormalArray(normals);

	for(int i = 0; i<FANCYCAR_VERTICE_COUNT;i++){
		//normal[0] = normals[i][0]; normal[1] = normals[i][1]; normal[2] = normals[i][2];
		//vertex[0] = vertices[i][0]; vertex[1] = vertices[i][1]; vertex[2] = vertices[i][2];

		vmu->AddVertex(FANCYCAR_VERTICES[i]);
		vmu->AddNormal(FANCYCAR_NORMALS[i]);
	}

	CheckGlError("GenSimpleCar");
}

void loadFloat3DArray(JNIEnv* env, jobject nativeObj, jclass hdClass, const char* method, H3DVector* output){
	jmethodID methodHandle = env->GetMethodID(hdClass,method,"()[F");

	if (methodHandle == NULL){
		__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error, could not load the java-method-handle into the ndk-environment.");
	}

	jfloatArray position = (jfloatArray)env->CallObjectMethod(nativeObj, methodHandle);

	if (position == NULL){
		__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error, could not load the float-array into the ndk-jfloatArray.");
	}

	jfloat* nativePos = env->GetFloatArrayElements(position, NULL);

	output[0][0] = (float)nativePos[0];
	output[0][1] = (float)nativePos[1];
	output[0][2] = (float)nativePos[2];

	env->ReleaseFloatArrayElements(position, nativePos, 0);
}

void loadFloat4DArray(JNIEnv* env, jobject nativeObj, jclass hdClass, const char* method, H4DVector* output){
	jmethodID methodHandle = env->GetMethodID(hdClass,method,"()[F");

	if (methodHandle == NULL){
		__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error, could not load the java-method-handle into the ndk-environment.");
	}

	jfloatArray position = (jfloatArray)env->CallObjectMethod(nativeObj, methodHandle);

	if (position == NULL){
		__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error, could not load the float-array into the ndk-jfloatArray.");
	}

	jfloat* nativePos = env->GetFloatArrayElements(position, NULL);

	output[0][0] = (float)nativePos[0];
	output[0][1] = (float)nativePos[1];
	output[0][2] = (float)nativePos[2];
	output[0][3] = (float)nativePos[3];

	env->ReleaseFloatArrayElements(position, nativePos, 0);
}


/**
 * This method pushes the provided id to the designated method in the java-code.
 * The method-handle is resolved using the given JNIEnvironment as well as the
 * jobject.
 */
/*void pushIdentifierToJava(JNIEnv *env, jobject obj, int id){

	jclass renderClass = env->GetObjectClass(obj);

	//Prepare the Method-Handle to push the detected trackable-ID's up to the Java-Side
	jmethodID methodHandle = env->GetMethodID(renderClass,"setcurrentTransMat","(I)V");

	if (methodHandle == NULL){
		__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error, could not load the java-method-handle to push the trackable-IDs into the ndk-environment.");
	}

	env->CallObjectMethod(obj, methodHandle, id);
}*/

int getCorrespondingMarker(JNIEnv *env, jobject obj, jclass hdClass){

	jmethodID methodHandle = env->GetMethodID(hdClass,"getMarkerID","()I");

	if (methodHandle == NULL){
		__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error, could not load the java-method-handle to get the markerID from the ndk-environment.");
	}

	return (int)env->CallIntMethod(obj, methodHandle);
}






/*H3DVector vertices[] = {
			coord,coord,coord,
			coord,coord,-coord,
			-coord,coord,-coord,

			coord,coord,coord,
			-coord,coord,-coord,
			-coord,coord,coord,

			-coord,-coord,-coord,
			coord,-coord,-coord,
			coord,-coord,coord,

			-coord,-coord,coord,
			-coord,-coord,-coord,
			coord,-coord,coord,

			-coord,coord,coord,
			-coord,coord,-coord,
			-coord,-coord,-coord,

			-coord,coord,coord,
			-coord,-coord,-coord,
			-coord,-coord,coord,

			coord,-coord,-coord,
			coord,coord,-coord,
			coord,coord,coord,

			coord,coord,coord,
			coord,-coord,coord,
			coord,-coord,-coord,

			coord,-coord,coord,
			coord,coord,coord,
			-coord,coord,coord,

			-coord,coord,coord,
			-coord,-coord,coord,
			coord,-coord,coord,

			coord,-coord,-coord,
			-coord,-coord,-coord,
			-coord,coord,-coord,

			-coord,coord,-coord,
			coord,coord,-coord,
			coord,-coord,-coord,
	};

	//Array of the necessary normals
	H3DVector normals[] = {
			0.0f,1.0f,0.0f,
			0.0f,1.0f,0.0f,
			0.0f,1.0f,0.0f,
			0.0f,1.0f,0.0f,
			0.0f,1.0f,0.0f,
			0.0f,1.0f,0.0f,
			0.0f,-1.0f,0.0f,
			0.0f,-1.0f,0.0f,
			0.0f,-1.0f,0.0f,
			0.0f,-1.0f,0.0f,
			0.0f,-1.0f,0.0f,
			0.0f,-1.0f,0.0f,
			-1.0f,0.0f,0.0f,
			-1.0f,0.0f,0.0f,
			-1.0f,0.0f,0.0f,
			-1.0f,0.0f,0.0f,
			-1.0f,0.0f,0.0f,
			-1.0f,0.0f,0.0f,
			1.0f,0.0f,0.0f,
			1.0f,0.0f,0.0f,
			1.0f,0.0f,0.0f,
			1.0f,0.0f,0.0f,
			1.0f,0.0f,0.0f,
			1.0f,0.0f,0.0f,
			0.0f,0.0f,1.0f,
			0.0f,0.0f,1.0f,
			0.0f,0.0f,1.0f,
			0.0f,0.0f,1.0f,
			0.0f,0.0f,1.0f,
			0.0f,0.0f,1.0f,
			0.0f,0.0f,-1.0f,
			0.0f,0.0f,-1.0f,
			0.0f,0.0f,-1.0f,
			0.0f,0.0f,-1.0f,
			0.0f,0.0f,-1.0f,
			0.0f,0.0f,-1.0f,
	};*/



